package ee.az.geometry;


/**
 * @author Artur Zinatullin (artur.zinatullin@gmail.com) 2010
 */
public class AngleArc {

	private static final AngleArc FULL_CIRCLE = new AngleArc(Double.NaN, Double.NaN);
	private final double lower;
	private final double higher;
	
	public AngleArc(double lower, double higher) {
		this.lower = Angle.normalize(lower);
		this.higher = Angle.normalize(higher);
	}

	public boolean contains(double angle) {
		if (isFullCircle()) {
			return true;
		}
		angle = Angle.normalize(angle);
		if (!isInverse()) {
			return lower <= angle && angle <= higher;
		} else {
			return lower <= angle || angle <= higher;
		}
	}

	public boolean isInverse() {
		return lower > higher;
	}
	
	public double getAngle() {
		if (isFullCircle()) {
			return 2*Math.PI;
		} else {
			return higher - lower + (isInverse() ? 2*Math.PI : 0);
		}
	}
	
	public boolean intersects(AngleArc arc) {
		return contains(arc.lower) || contains(arc.higher) || arc.contains(lower) || arc.contains(higher);
	}

	public boolean contains(AngleArc arc) {
		return contains(arc.lower) && contains(arc.higher);
	}
	
	public boolean isFullCircle() {
		return lower == Double.NaN && higher == Double.NaN;
	}
	
	/**
	 * @return if this arc and parameter arc do cover full circle together
	 */
	public boolean coverFullCircle(AngleArc arc) {
		return isFullCircle() || arc.isFullCircle() || (contains(arc.lower) && contains(arc.higher) && (isInverse() ^ arc.isInverse()));
	}
	
	public AngleArc sum(AngleArc arc) {
		if (!intersects(arc)) {
			throw new IllegalArgumentException("Cannot sum, arcs do not intersect");
		} else if (coverFullCircle(arc)) {
			return FULL_CIRCLE;
		} else if (!contains(arc.lower) && !contains(arc.higher)) {
			return arc.sum(this);
		} else if (contains(arc)) {
			return new AngleArc(lower, higher); // copy of this
		} else {
			if (contains(arc.lower)) {
				return new AngleArc(lower, arc.higher);
			} else {
				return new AngleArc(arc.lower, higher);
			}
		}
	}
	
	/**
	 * Gets angle arc with center at center point of given center spot (e.g centerX+0.5, centerY+0.5)
	 * covering all the tpot given by (x, y)
	 */
	public static AngleArc getArc(int x, int y, int centerX, int centerY) {
		double[] angles = new double [] {
				Angle.normalize(Angle.getAngle(x, y, centerX+0.5, centerY+0.5)),
				Angle.normalize(Angle.getAngle(x+1, y, centerX+0.5, centerY+0.5)),
				Angle.normalize(Angle.getAngle(x, y+1, centerX+0.5, centerY+0.5)),
				Angle.normalize(Angle.getAngle(x+1, y+1, centerX+0.5, centerY+0.5))
		};
		AngleArc result = getArc(angles);
		if (result.getHigher() - result.getLower() > Math.PI) { // angle around 0
			for(int i=0; i<angles.length; i++) {
				if (angles[i] > Math.PI) {
					angles[i] -= 2*Math.PI;
				}
			}
			result = getArc(angles);
		}
		return result;
	}
	
	public static AngleArc getArc(double[] angles) {
		double low = Double.POSITIVE_INFINITY;
		double high = Double.NEGATIVE_INFINITY;
		for (double a : angles) {
			low = Math.min(low, a);
			high = Math.max(high, a);
		}
		return new AngleArc(low, high);
	}
	
	@Override
	public String toString() {
		return "arc["+ lower/Math.PI + "π, " + higher/Math.PI + "π]";
	}
	
	public double getLower() {
		return lower;
	}
	public double getHigher() {
		return higher;
	}
}
